﻿//BlackScholes.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Derivatives
{
	using System;

	using alglib;

	using Ecng.Common;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Модель расчета значений "греков" по формуле Блэка-Шоулза.
	/// </summary>
	public class BlackScholes : IBlackScholes
	{
		/// <summary>
		/// Инициализировать <see cref="BlackScholes"/>.
		/// </summary>
		protected BlackScholes()
		{
		}

		/// <summary>
		/// Создать <see cref="BlackScholes"/>.
		/// </summary>
		/// <param name="option">Опцион.</param>
		public BlackScholes(Security option)
		{
			if (option == null)
				throw new ArgumentNullException("option");

			Option = option;
		}

		/// <summary>
		/// Опцион.
		/// </summary>
		public virtual Security Option { get; private set; }

		/// <summary>
		/// Безрисковая процентная ставка.
		/// </summary>
		public decimal RiskFree { get; set; }

		/// <summary>
		/// Размер дивиденда по акциям.
		/// </summary>
		public decimal Dividend { get; set; }

		private int _roundDecimals = -1;

		/// <summary>
		/// Количество знаков после запятой у вычисляемых значений. По-умолчанию равно -1, что означает не округлять значения.
		/// </summary>
		public virtual int RoundDecimals
		{
			get { return _roundDecimals; }
			set
			{
				if (value < -1)
					throw new ArgumentOutOfRangeException("value", value, "Количество знаков после запятой не верно.");

				_roundDecimals = value;
			}
		}

		private Func<Security, decimal> _securityPriceMode = SecurityLastTradePrice;

		/// <summary>
		/// Вариант определения цены инструмента. По-умолчанию равно <see cref="SecurityLastTradePrice"/>.
		/// </summary>
		public Func<Security, decimal> SecurityPriceMode
		{
			get { return _securityPriceMode; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_securityPriceMode = value;
			}
		}

		/// <summary>
		/// Вариант определения цены инструмента по цене последней сделки <see cref="Security.LastTrade"/>.
		/// </summary>
		public static readonly Func<Security, decimal> SecurityLastTradePrice = s => s.LastTrade != null ? s.LastTrade.Price : 0;

		/// <summary>
		/// Вариант определения цены инструмента по цене лучшего бида <see cref="Security.BestBid"/>.
		/// </summary>
		public static readonly Func<Security, decimal> SecurityBestBidPrice = s => s.BestBid != null ? s.BestBid.Price : 0;

		/// <summary>
		/// Вариант определения цены инструмента по цене лучшего оффера <see cref="Security.BestAsk"/>.
		/// </summary>
		public static readonly Func<Security, decimal> SecurityBestAskPrice = s => s.BestAsk != null ? s.BestAsk.Price : 0;

		/// <summary>
		/// Вариант определения цены инструмента по середине спреда.
		/// </summary>
		public static readonly Func<Security, decimal> SecuritySpreadPrice = s => s.BestPair.MiddlePrice;

		private Security _underlyingAsset;

		/// <summary>
		/// Базовый актив.
		/// </summary>
		public virtual Security UnderlyingAsset
		{
			get { return _underlyingAsset ?? (_underlyingAsset = Option.GetUnderlyingAsset()); }
		}

		private decimal DefaultDeviation
		{
			get { return Option.ImpliedVolatility / 100; }
		}

		/// <summary>
		/// Расчет времени до экспирации.
		/// </summary>
		/// <returns>Время, оставшееся до экспирации.</returns>
		public virtual double GetExpirationTimeLine()
		{
			var expirationTime = Option.GetExpirationTime();
			var marketTime = Option.GetMarketTime();

			var retVal = (expirationTime - marketTime).TotalDays;

			if (retVal <= 0)
				throw new InvalidOperationException("Время экспирации {0} для {1} прошло. Текущая дата {2}.".Put(expirationTime, Option, marketTime));

			return retVal / _dayInYear;
		}

		private const int _dayInYear = 365; // Количество дней в году (расчет временного распада)

		private double D1(decimal deviation)
		{
			if (deviation < 0)
				throw new ArgumentOutOfRangeException("deviation", deviation, "Стандартное отклонение имеет недопустимое значение.");

			var timeToExp = GetExpirationTimeLine();

			return (((double)SecurityPriceMode(UnderlyingAsset) / (double)Option.Strike).Log() +
				(double)(RiskFree - Dividend + deviation * deviation / 2.0m) * timeToExp) / ((double)deviation * timeToExp.Sqrt());
		}

		private double D2(double d1, decimal deviation)
		{
			var timeToExp = GetExpirationTimeLine();
			return d1 - (double)deviation * timeToExp.Sqrt();
		}

		private static double InvertD1(double d1)
		{
			// http://ru.wikipedia.org/wiki/Нормальное_распределение (сигма=1 и мю=0)
			return Math.Exp(-d1 * d1 / 2.0) / Math.Sqrt(2 * Math.PI);
		}

		private decimal TryRound(decimal value)
		{
			if (RoundDecimals >= 0)
				value = Math.Round(value, RoundDecimals);

			return value;
		}

		/// <summary>
		/// Рассчитать премию опциона.
		/// </summary>
		/// <returns>Премия опциона.</returns>
		public virtual decimal Premium()
		{
			return Premium(DefaultDeviation);
		}

		/// <summary>
		/// Рассчитать премию опциона.
		/// </summary>
		/// <param name="deviation">Стандартное отклонение.</param>
		/// <returns>Премия опциона.</returns>
		public virtual decimal Premium(decimal deviation)
		{
			var sign = (Option.OptionType == OptionTypes.Call) ? 1 : -1;

			var timeToExp = GetExpirationTimeLine();

			var expRate = RiskFree == 0 ? 1 : Math.Exp(-(double)RiskFree * timeToExp);
			var expDiv = Dividend == 0 ? 1 : Math.Exp(-(double)Dividend * timeToExp);

			var d1 = D1(deviation);
			var d2 = D2(d1, deviation);

			var premium = (SecurityPriceMode(UnderlyingAsset) * (decimal)(expDiv * normaldistr.normaldistribution(d1 * sign)) -
			               Option.Strike * (decimal)(expRate * normaldistr.normaldistribution(d2 * sign))) * sign;

			return TryRound(premium);
		}

		/// <summary>
		/// Рассчитать дельту опциона.
		/// </summary>
		/// <returns>Дельта опциона.</returns>
		public virtual decimal Delta()
		{
			return Delta(DefaultDeviation);
		}

		/// <summary>
		/// Рассчитать дельту опциона.
		/// </summary>
		/// <param name="deviation">Стандартное отклонение.</param>
		/// <returns>Дельта опциона.</returns>
		public virtual decimal Delta(decimal deviation)
		{
			var delta = (decimal)normaldistr.normaldistribution(D1(deviation));

			if (Option.OptionType == OptionTypes.Put)
				delta -= 1;

			return TryRound(delta);
		}

		/// <summary>
		/// Рассчитать гамму опциона.
		/// </summary>
		/// <returns>Гамма опциона.</returns>
		public virtual decimal Gamma()
		{
			return Gamma(DefaultDeviation);
		}

		/// <summary>
		/// Рассчитать гамму опциона.
		/// </summary>
		/// <param name="deviation">Стандартное отклонение.</param>
		/// <returns>Гамма опциона.</returns>
		public virtual decimal Gamma(decimal deviation)
		{
			if (deviation == 0)
				return 0;
				//throw new ArgumentOutOfRangeException("deviation", deviation, "Стандартное отклонение имеет недопустимое значение.");

			var nd1 = InvertD1(D1(deviation));
			var timeToExp = GetExpirationTimeLine();

			var basePrice = SecurityPriceMode(UnderlyingAsset);

			if (basePrice == 0)
				return 0;

			var gamma = (decimal)nd1 / (basePrice * deviation * (decimal)timeToExp.Sqrt());

			return TryRound(gamma);
		}

		/// <summary>
		/// Рассчитать вегу опциона.
		/// </summary>
		/// <returns>Вега опциона.</returns>
		public virtual decimal Vega()
		{
			return Vega(DefaultDeviation);
		}

		/// <summary>
		/// Рассчитать вегу опциона.
		/// </summary>
		/// <param name="deviation">Стандартное отклонение.</param>
		/// <returns>Вега опциона.</returns>
		public virtual decimal Vega(decimal deviation)
		{
			var nd1 = InvertD1(D1(deviation));
			var timeToExp = GetExpirationTimeLine();

			var vega = SecurityPriceMode(UnderlyingAsset) * (decimal)(0.01 * nd1 * timeToExp.Sqrt());

			return TryRound(vega);
		}

		/// <summary>
		/// Рассчитать тету опциона.
		/// </summary>
		/// <returns>Тета опциона.</returns>
		public virtual decimal Theta()
		{
			return Theta(DefaultDeviation);
		}

		/// <summary>
		/// Рассчитать тету опциона.
		/// </summary>
		/// <param name="deviation">Стандартное отклонение.</param>
		/// <returns>Тета опциона.</returns>
		public virtual decimal Theta(decimal deviation)
		{
			var d1 = D1(deviation);
			var d2 = D2(d1, deviation);
			var nd1 = InvertD1(d1);

			var timeToExp = GetExpirationTimeLine();
			var expRate = RiskFree == 0 ? 1 : Math.Exp(-(double)RiskFree * timeToExp);

			var sign = Option.OptionType == OptionTypes.Call ? 1 : -1;

			var theta =
				-(SecurityPriceMode(UnderlyingAsset) * deviation * (decimal)nd1) / (2 * (decimal)timeToExp.Sqrt() * _dayInYear) -
				sign * (Option.Strike * RiskFree * (decimal)(expRate * normaldistr.normaldistribution(sign * d2)));

			return TryRound(theta);
		}

		/// <summary>
		/// Рассчитать ро опциона.
		/// </summary>
		/// <returns>Ро опциона.</returns>
		public virtual decimal Rho()
		{
			return Rho(DefaultDeviation);
		}

		/// <summary>
		/// Рассчитать ро опциона.
		/// </summary>
		/// <param name="deviation">Стандартное отклонение.</param>
		/// <returns>Ро опциона.</returns>
		public virtual decimal Rho(decimal deviation)
		{
			var d1 = D1(deviation);
			var d2 = D2(d1, deviation);

			var timeToExp = GetExpirationTimeLine();
			var expRate = RiskFree == 0 ? 1 : Math.Exp(-(double)RiskFree * timeToExp);

			var sign = Option.OptionType == OptionTypes.Call ? 1 : -1;

			var rho = sign * (0.01m * Option.Strike * (decimal)(timeToExp * expRate * normaldistr.normaldistribution(sign * d2)));

			return TryRound(rho);
		}

		/// <summary>
		/// Рассчитать подразумеваемую волатильность (Implied  Volatility).
		/// </summary>
		/// <param name="premium">Премия по опциону.</param>
		/// <returns>Подразумеваевая волатильность.</returns>
		public virtual decimal IV(decimal premium)
		{
			const decimal min = 0.00001m;

			//Если Премия оказывается меньше чем премия с нулевой волатильностью, то выходим
			if (premium <= Premium(min))
				return 0;

			var high = 2m;
			var low = 0m;

			while ((high - low) > min)
			{
				var deviation = (high + low) / 2;

				if (Premium(deviation) > premium)
					high = deviation;
				else
					low = deviation;
			}

			return TryRound(((high + low) / 2) * 100);
		}

		/// <summary>
		/// Создать стакан волатильности.
		/// </summary>
		/// <returns>Стакан волатильности.</returns>
		public virtual MarketDepth IV()
		{
			return Option.CheckTrader().GetMarketDepth(Option).IV(this);
		}
	}
}